﻿using System;
using System.Collections.Generic;
using AgateLib.DisplayLib;
using AgateLib.Geometry;
using AiAsteriods.Game.Entities.Base;
using AiAsteriods.Game.Entities.Enemies;
using AiAsteriods.Game.Entities.Projectile;
using AiAsteriods.Game.State;
using AiAsteriods.Game.Tools;

namespace AiAsteriods.Game.Entities.Agent {
  static class StateGrid {
    private static FontSurface fontSmall;
    private static Config config;
    private static GridContentType[][,] grids;
    private static Pos[] playerPos;
    private static int numPlayers;

    public static void Init(int numPlayers) {
      config = Global.GameStart.Config;
      fontSmall = new AgateLib.DisplayLib.FontSurface("Verdana", 100 / config.GridRes);

      StateGrid.numPlayers = numPlayers;

      playerPos = new Pos[numPlayers];
      grids = new GridContentType[numPlayers][,];
      for (int i = 0; i < numPlayers; i++)
        grids[i] = new GridContentType[(int)config.GridRes, (int)config.GridRes];
    }

    private static void ClearGrid(int gridNum) {
      for (int j = 0; j < config.GridRes; j++)
        for (int k = 0; k < config.GridRes; k++)
          grids[gridNum][j, k] = GridContentType.BLANK;
    }

    public static void CalcGrid(int gridNum, GameStateAbstract gs) {
      double cw = Global.GameStart.ScreenWidth / (double)config.GridRes;
      double ch = Global.GameStart.ScreenHeight / (double)config.GridRes;

      ClearGrid(gridNum);

      foreach (EntityAbstract ea in gs.Entities) {
        if (ea != null && ea.Y > 0 && ea.X > 0 && ea.Y < Global.GameStart.ScreenHeight && ea.X < Global.GameStart.ScreenWidth) {
          int r = (int)(ea.Y / ch);
          int c = (int)(ea.X / cw);

          GridContentType addType = GridContentType.BLANK;
          if (ea is Bullet) {
            addType = GridContentType.BULLET;
          }
          else if (ea is Player) {
            playerPos[((Player)ea).PlayerNum] = new Pos(c, r);
            addType = GridContentType.PLAYER;
          }
          else if (ea is Enemy) {
            addType = GridContentType.ENEMY;
          }

          grids[gridNum][r, c] |= addType;
        }
      }

      MarkRLRS(gridNum);
      MarkRC(gridNum);
    }

    private static void MarkRLRS(int gridNum) {
      FindRLRS(gridNum, Global.GameStart.CurrentState.Player1.Rotation, false);
      if (Player.IsAwesome)
        FindRLRS(gridNum, Global.GameStart.CurrentState.Player1.Rotation + 180, true);
    }

    private static void FindRLRS(int gridNum, double origRot, bool swap) {
      int lrRegionAngle = Global.GameStart.Config.RLRAngle;
      int sRegionAngle = Global.GameStart.Config.RSAngle;
      var leftSlopes = new List<PosF>();
      var rightSlopes = new List<PosF>();
      var bulletSlopes = new List<PosF>();
      double pr, pc;
      const double rotStep = 0.1;

      for (double rot = origRot - (lrRegionAngle + sRegionAngle); rot < origRot - sRegionAngle; rot += rotStep) {
        double rd, cd;
        RotToSlope(rot, out rd, out cd);
        if (swap)
          rightSlopes.Add(new PosF((float)cd, (float)rd));
        else
          leftSlopes.Add(new PosF((float)cd, (float)rd));
      }

      for (double rot = origRot - sRegionAngle; rot < origRot + sRegionAngle; rot += rotStep) {
        double rd, cd;
        RotToSlope(rot, out rd, out cd);
        bulletSlopes.Add(new PosF((float)cd, (float)rd));
      }

      for (double rot = origRot + sRegionAngle; rot < origRot + (lrRegionAngle + sRegionAngle); rot += rotStep) {
        double rd, cd;
        RotToSlope(rot, out rd, out cd);
        if (swap)
          leftSlopes.Add(new PosF((float)cd, (float)rd));
        else
          rightSlopes.Add(new PosF((float)cd, (float)rd));
      }

      foreach (PosF p in leftSlopes) {
        pr = playerPos[gridNum].r;
        pc = playerPos[gridNum].c;
        while ((int)pr >= 0 && (int)pr < config.GridRes && (int)pc >= 0 && (int)pc < config.GridRes) {
          grids[gridNum][(int)pr, (int)pc] |= GridContentType.RL;
          pr += p.r;
          pc += p.c;
        }
      }

      foreach (PosF p in bulletSlopes) {
        pr = playerPos[gridNum].r;
        pc = playerPos[gridNum].c;
        while ((int)pr >= 0 && (int)pr < config.GridRes && (int)pc >= 0 && (int)pc < config.GridRes) {
          grids[gridNum][(int)pr, (int)pc] |= GridContentType.RS;
          pr += p.r;
          pc += p.c;
        }
      }

      foreach (PosF p in rightSlopes) {
        pr = playerPos[gridNum].r;
        pc = playerPos[gridNum].c;
        while ((int)pr >= 0 && (int)pr < config.GridRes && (int)pc >= 0 && (int)pc < config.GridRes) {
          grids[gridNum][(int)pr, (int)pc] |= GridContentType.RR;
          pr += p.r;
          pc += p.c;
        }
      }
    }

    private static void MarkRC(int gridNum) {
      double rd, cd;
      PlayerRotToSlope(gridNum, out rd, out cd);
      double m = -cd / rd;
      double b = playerPos[gridNum].y - m * playerPos[gridNum].x - NumCellsFromPixelWidth(30);

      foreach (Pos p in CircleGridAroundPlayer(gridNum, config.RCRadius)) {
        Pos ptemp = p;

        if (config.RCLoop) {
          ptemp.r %= config.GridRes;
          ptemp.c %= config.GridRes;

          if (ptemp.r < 0)
            ptemp.r += config.GridRes;
          if (ptemp.c < 0)
            ptemp.c += config.GridRes;
        }
        else {
          if (ptemp.r < 0 || ptemp.r >= config.GridRes || ptemp.c < 0 || ptemp.c >= config.GridRes)
            continue;
        }

        double pb = ptemp.y - m * ptemp.x;

        if (pb > b)
          grids[gridNum][ptemp.r, ptemp.c] |= GridContentType.RF;
        else
          grids[gridNum][ptemp.r, ptemp.c] |= GridContentType.RA;
      }
    }

    public static int NumCellsFromPixelWidth(int pixels) {
      double cw = Global.GameStart.ScreenWidth / (double)config.GridRes;
      return (int)Math.Ceiling(pixels / cw);
    }

    private static void PlayerRotToSlope(int gridNum, out double rd, out double cd) {
      RotToSlope(Global.GameStart.CurrentState.Player1.Rotation, out rd, out cd);
    }

    private static void RotToSlope(double rot, out double rd, out double cd) {
      if (rot < 0)
        rot += 360;
      if (rot > 360)
        rot %= 360;

      if (rot <= 45) {
        rd = 1;
        cd = rot / 45;
      }
      else if (rot <= 90) {
        rd = 1 - ((rot - 45) / 45);
        cd = 1;
      }
      else if (rot <= 135) {
        rd = -((rot - 90) / 45);
        cd = 1;
      }
      else if (rot <= 180) {
        rd = -1;
        cd = 1 - ((rot - 135) / 45);
      }
      else if (rot <= 225) {
        rd = -1;
        cd = -((rot - 180) / 45);
      }
      else if (rot <= 270) {
        rd = -1 + ((rot - 225) / 45);
        cd = -1;
      }
      else if (rot <= 315) {
        rd = (rot - 270) / 45;
        cd = -1;
      }
      else {
        rd = 1;
        cd = -1 + ((rot - 315) / 45);
      }
    }

    public static void GetRegionCounts(int gridNum, out float rl, out float rr, out float ra, out float rf, out float rs) {
      rl = rr = ra = rf = rs = 0;

      float maxDist = (new Pos(0, 0)).DistTo(new Pos(config.GridRes, config.GridRes));

      for (int i = 0; i < config.GridRes; i++) {
        for (int j = 0; j < config.GridRes; j++) {
          if ((grids[gridNum][i, j] & GridContentType.ENEMY) != 0) {
            float dist = (float)Math.Pow(maxDist - playerPos[gridNum].DistTo(new Pos(j, i)), config.EnemyProximityMult);
            if ((grids[gridNum][i, j] & GridContentType.RL) != 0) {
              rl += dist;
            }
            if ((grids[gridNum][i, j] & GridContentType.RR) != 0) {
              rr += dist;
            }
            if ((grids[gridNum][i, j] & GridContentType.RA) != 0) {
              ra += dist;
            }
            if ((grids[gridNum][i, j] & GridContentType.RF) != 0) {
              rf += dist;
            }
            if ((grids[gridNum][i, j] & GridContentType.RS) != 0) {
              rs += dist;
            }
          }
        }
      }
    }

    public static List<Pos> CircleGridAroundPlayer(int playerNum, int radius) {
      var result = new List<Pos>();
      for (int rdev = -radius; rdev < radius; rdev++) {
        for (int cdev = -radius; cdev < radius; cdev++) {
          if (Dist(0, rdev, 0, cdev) < radius)
            result.Add(new Pos(playerPos[playerNum].c + cdev, playerPos[playerNum].r + rdev));
        }
      }

      return result;
    }

    public static List<Pos> RectGridAroundPlayer(int playerNum, int size) {
      var result = new List<Pos>();
      for (int rdev = -size; rdev < size; rdev++) {
        for (int cdev = -size; cdev < size; cdev++) {
          result.Add(new Pos(playerPos[playerNum].c + cdev, playerPos[playerNum].r + rdev));
        }
      }

      return result;
    }

    private static double Dist(int x1, int x2, int y1, int y2) {
      return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
    }

    public static void DisplayGrid(int gridNum) {
      float sp = 100.0f / config.GridRes;
      float init_lift = Global.GameStart.ScreenHeight - 10 - (config.GridRes * sp);

      float shift = 10 + ((sp * config.GridRes + 10) * gridNum);
      for (int j = 0; j < config.GridRes; j++) {
        float lift = init_lift + (sp * j);
        for (int k = 0; k < config.GridRes; k++) {
          string disp = "+";

          Color fg = Color.Black;
          if ((grids[gridNum][j, k] & GridContentType.RS) != 0) {
            fg.R = 0;
            fg.G = 0;
            fg.B = 255;
          }
          else if ((grids[gridNum][j, k] & GridContentType.RL) != 0) {
            fg.R = 255;
            fg.G = 0;
            fg.B = 0;
          }
          else if ((grids[gridNum][j, k] & GridContentType.RR) != 0) {
            fg.R = 255;
            fg.G = 255;
            fg.B = 0;
          }
          else if ((grids[gridNum][j, k] & GridContentType.RA) != 0) {
            fg.R = 0;
            fg.G = 255;
            fg.B = 0;
          }
          else if ((grids[gridNum][j, k] & GridContentType.RF) != 0) {
            fg.R = 255;
            fg.G = 255;
            fg.B = 255;
          }

          if ((grids[gridNum][j, k] & GridContentType.ENEMY) != 0) {
            fg.R = (byte)(255 - fg.R);
            fg.G = (byte)(255 - fg.G);
            fg.B = (byte)(255 - fg.B);
          }

          fontSmall.Color = fg;
          fontSmall.DrawText(shift + (sp * k), lift, disp);
        }
      }
    }
  }
}
